<!DOCTYPE html>
<meta charset="utf-8">
<title>PivotGraph (Rollup) Layout</title>
<style>

body {
  font-family: sans-serif;
  font-size: 10px;
}

path {
  fill: none;
  stroke: #ccc;
}

circle {
  fill: #fff;
  stroke: steelblue;
}

</style>
<body>
<script src="../../d3.v2.js"></script>
<script>

var social = {
  nodes: [
    {gender: "M", group: 1},
    {gender: "F", group: 2},
    {gender: "M", group: 1},
    {gender: "M", group: 2},
    {gender: "F", group: 2},
    {gender: "M", group: 1},
    {gender: "F", group: 1},
    {gender: "M", group: 2},
    {gender: "F", group: 2},
    {gender: "F", group: 2}
  ],
  links: [
    {source: 0, target: 1},
    {source: 1, target: 2},
    {source: 2, target: 3},
    {source: 2, target: 4},
    {source: 2, target: 5},
    {source: 2, target: 6},
    {source: 2, target: 7},
    {source: 5, target: 6},
    {source: 6, target: 7},
    {source: 5, target: 8},
    {source: 8, target: 6},
    {source: 6, target: 9}
  ]
};

var width = 100,
    height = 100,
    margin = 30;

var x = d3.scale.ordinal()
    .domain(social.nodes.map(fx))
    .range([0, width]);

var y = d3.scale.ordinal()
    .domain(social.nodes.map(fy))
    .range([0, height]);

function fx(d) { return d.gender; }
function fy(d) { return d.group; }

var vis = d3.select("body").append("svg")
    .attr("width", width + 2 * margin)
    .attr("height", height + 2 * margin)
  .append("g")
    .attr("transform", "translate(" + [margin, margin] + ")");

var layout = rollup()
    .x(function(d) { return x(fx(d)); })
    .y(function(d) { return y(fy(d)); });

var g = layout(social);

vis.selectAll("path")
    .data(g.links)
  .enter().append("path")
    .style("stroke-width", function(d) { return d.value * 1.5; })
    .attr("d", function(d) {
      var tx = d.target.x,
          sx = d.source.x,
          ty = d.target.y,
          sy = d.source.y,
          dx = tx - sx,
          dy = ty - sy,
          dr = 2 * Math.sqrt(dx * dx + dy * dy);
      return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
    });

vis.selectAll("circle")
    .data(g.nodes)
  .enter().append("circle")
    .attr("r", function(d) { return Math.sqrt(d.nodes.length * 20); })
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });

vis.selectAll("text.x")
    .data(x.domain())
  .enter().append("text")
    .attr("class", "x")
    .attr("x", x)
    .attr("y", -10)
    .attr("text-anchor", "middle")
    .text(String);

vis.selectAll("text.y")
    .data(y.domain())
  .enter().append("text")
    .attr("class", "y")
    .attr("x", -10)
    .attr("dy", ".3em")
    .attr("y", y)
    .attr("text-anchor", "end")
    .text(String);

function rollup() {
  var directed = true,
      x_ = rollupX,
      y_ = rollupY,
      nodes_ = rollupNodes,
      links_ = rollupLinks,
      linkValue = rollupLinkValue,
      linkSource = rollupLinkSource,
      linkTarget = rollupLinkTarget;

  function rollup(d, i) {
    var nodes = nodes_.call(this, d, i),
        links = links_.call(this, d, i),
        n = nodes.length,
        m = links.length,
        i = -1,
        x = [],
        y = [],
        rnindex = 0,
        rnodes = {},
        rlinks = {};

    // Compute rollup nodes.
    while (++i < n) {
      (d = nodes[i]).index = i;
      x[i] = x_.call(this, d, i);
      y[i] = y_.call(this, d, i);
      var nodeId = id(i),
          rn = rnodes[nodeId];
      if (!rn) {
        rn = rnodes[nodeId] = {
          index: rnindex++,
          x: x[i],
          y: y[i],
          nodes: []
        };
      }
      rn.nodes.push(d);
    }

    // Compute rollup links.
    i = -1; while (++i < m) {
      var value = linkValue.call(this, d = links[i], i),
          source = linkSource.call(this, d, i),
          target = linkTarget.call(this, d, i),
          rsource = rnodes[id(typeof source === "number" ? source : source.index)],
          rtarget = rnodes[id(typeof target === "number" ? target : target.index)],
          linkId = !directed && rsource.index > rtarget.index
              ? rtarget.index + "," + rsource.index
              : rsource.index + "," + rtarget.index,
          rl = rlinks[linkId];
      if (!rl) {
        rl = rlinks[linkId] = {
          source: rsource,
          target: rtarget,
          value: 0,
          links: []
        };
      }
      rl.links.push(links[i]);
      rl.value += value;
    }

    return {
      nodes: d3.values(rnodes),
      links: d3.values(rlinks)
    };

    function id(i) {
      return x[i] + "," + y[i];
    }
  }

  rollup.x = function(x) {
    if (!arguments.length) return x_;
    x_ = x;
    return rollup;
  };

  rollup.y = function(x) {
    if (!arguments.length) return y_;
    y_ = x;
    return rollup;
  };

  rollup.nodes = function(x) {
    if (!arguments.length) return nodes_;
    nodes_ = x;
    return rollup;
  };

  rollup.links = function(x) {
    if (!arguments.length) return links_;
    links_ = x;
    return rollup;
  };

  rollup.linkSource = function(x) {
    if (!arguments.length) return linkSource;
    linkSource = x;
    return rollup;
  };

  rollup.linkTarget = function(x) {
    if (!arguments.length) return linkTarget;
    linkTarget = x;
    return rollup;
  };

  rollup.linkValue = function(x) {
    if (!arguments.length) return linkValue;
    linkValue = x;
    return rollup;
  };

  rollup.directed = function(x) {
    if (!arguments.length) return directed;
    directed = x;
    return rollup;
  };

  return rollup;

  function rollupX(d) { return d.x; }
  function rollupY(d) { return d.y; }
  function rollupNodes(d) { return d.nodes; }
  function rollupLinks(d) { return d.links; }
  function rollupLinkValue(d) { return 1; }
  function rollupLinkSource(d) { return d.source; }
  function rollupLinkTarget(d) { return d.target; }
}

</script>
